#ifndef DOG_128_h
#define DOG_128_h

#define GLCD_VERSION 1 // software version of this library

#include "WProgram.h"
#include <avr/pgmspace.h>

typedef struct {
	byte x;
	byte y;
	byte page;
} lcdCoord;

typedef byte (*FontCallback)(const byte*);

byte ReadPgmData(const byte* ptr);	//Standard Read Callback

// Colors
static const byte BLACK = 0xFF;
static const byte WHITE = 0x00;

// useful user contants
static const byte NON_INVERTED = 0;
static const byte INVERTED = 1;

// Font Indices
static const byte FONT_LENGTH = 0;
static const byte FONT_FIXED_WIDTH = 2;
static const byte FONT_HEIGHT = 3;
static const byte FONT_FIRST_CHAR = 4;
static const byte FONT_CHAR_COUNT = 5;
static const byte FONT_WIDTH_TABLE = 6;

class Dog128
{
	public:
		static const byte DISPLAY_HEIGHT = 64;
		static const byte DISPLAY_WIDTH = 128;
		static const byte DISPLAY_PAGES = 8;

	private:
		byte data[DISPLAY_PAGES*DISPLAY_WIDTH];
		byte update[DISPLAY_PAGES*DISPLAY_WIDTH/8];
		
	    lcdCoord Coord;
		FontCallback FontRead;
		byte FontColor;
		const byte*	Font;
		byte inverted;
		
		/** The (arduino-)pin used for the serial data */
		byte SI;
		/** The (arduino-)pin used for the serial clock */
		byte SCL;
		/** The (arduino-)pin that toggles between sending a command or graphic data to the display */
		byte A0;
		/** The (arduino-)pin used selecting the display */
		byte CS1B;
		/** The (arduino-)pin used for resetting the dislay */
		byte RESET;
		/** The (arduino-)pin used for switching the backlight */
		byte backLight;
		
	public:
		Dog128(byte SI=11, byte SCL=13, byte A0=9, byte CS1B=10, byte RESET=-1, byte backLight=-1);
		
		/**
		 * Set the backlight. This is obviously only possible
		 * if you have build a small circuit for switching/dimming the 
		 * backlight of the display. 
		 * @param value the new value for the backlight. If the 
		 * backlight driver circuit is connected to a normal digital IO-pin
		 * on the arduino using value=LOW or value=HIGH will switch
		 * off/on as expected. 
		 * @param usePWM set this to true if the driver is connected to  
		 * a PWM pin on the arduino you can set any dim the backlight
		 * with values from 0..255
		 */
		void setBacklight(int value,bool PWM=false);
		
		/**
		 * Reset the display.
		 */
		void reset();

		/**
		 * Set the contrast for the display.
		 * @param contrast the contrast to be used for the display. Setting 
		 * the contrast to a value < 32 will probably give the impression
		 * that the display isn't working because nothing is printed.  
		 * The displays that I tested worked firn with a contrast >= 0x28.
		 * The valid range for the contrast value is 0.63 (0x00..0x3F hex).
		 * If the value is outside the valid range the method does nothing.
		 */
		void setContrast(byte contrast);
		
		// Control functions
		void Init(byte invert);
		void SetInverted(byte invert);
		void GotoXY(byte x, byte y);
		void Update();
		
		// Graphic Functions
		void ClearPage(byte page, byte color);
		void ClearScreen(byte color = WHITE);
		void RefreshScreen();
		void DrawLine(byte x1, byte y1, byte x2, byte y2, byte color);
		void DrawRect(byte x, byte y, byte width, byte height, byte color);
		void DrawRoundRect(byte x, byte y, byte width, byte height, byte radius, byte color);
		void FillRect(byte x, byte y, byte width, byte height, byte color);
		void InvertRect(byte x, byte y, byte width, byte height);
		void DrawVertLine(byte x, byte y, byte length, byte color);
		void DrawHoriLine(byte x, byte y, byte length, byte color);
		void DrawCircle(byte xCenter, byte yCenter, byte radius, byte color);
		void ClearScreenX();
		void ClearSysTextLine(byte line);
		void SetDot(byte x, byte y, byte color);
		void DrawBitmap(const uint8_t *bitmap, byte x, byte y, byte color);

		// Font Functions
		void SelectFont(const byte* font, byte color=BLACK, FontCallback callback=ReadPgmData); // defualt arguments added, callback now last arg
		byte PutChar(char c);
		void Puts(const char* str);
		void Puts_P(PGM_P str);
		void PrintNumber(long n, byte base = 10, byte fill = 1, byte sign = 1);
		void PrintNumber(float n, byte decimals);
		void CursorTo(byte x, byte y); // 0 based coordinates for fixed width fonts (i.e. systemFont5x7)

		byte CharWidth(char c);
		int StringWidth(char* str);
		int StringWidth_P(PGM_P str);
		
	private:
		byte ReadData(void); 
		byte DoReadData(byte first);
		void WriteData(byte data); // experts can make this public but the functionality is not documented

		//byte getData(byte x, byte y);
		//void setData(byte x, byte y, byte d);

		void setPageColumn(byte page, byte column);
		
		/**
		 * Send a command to the display
		 * @param cmd the command to send.
		 * @param executionTime the hardware needs some time to
		 * execute the instruction just send. This is the time in
		 * microseconds the code should wait after transfering the data
		 */
		void writeCommand(byte cmd, byte executionTime=30);

		/**
		 * Send data to the display 
		 * @param value the data to send.
		 */
		void writeData(byte value);

		/**
		 * Implements the low-level transfer of the data
		 * to the hardware.
		 * @param executionTime the hardware needs some time to
		 * execute the instruction just send. This is the time in
		 * microseconds the code should wait after transfering the data
		 */
		void spiTransfer(byte value,byte executionTime=30);
};

#endif
